use jsonwebtoken::{encode, EncodingKey, Header, Algorithm};
use chrono::{Utc, Duration};
use uuid::Uuid;
use sea_orm::{ActiveModelTrait, Set};

use crate::{
    config::AppConfig,
    error::{AppError, AppResult},
    middleware::auth::Claims,
    models::user::*,
    services::UserService,
};

#[derive(Clone)]
pub struct AuthService {
    user_service: UserService,
    config: AppConfig,
}

impl AuthService {
    pub fn new(user_service: UserService, config: AppConfig) -> Self {
        Self {
            user_service,
            config,
        }
    }

    pub async fn login(&self, req: LoginRequest) -> AppResult<LoginResponse> {
        // Find user by email
        let user = self.user_service
            .find_by_email(&req.email)
            .await?
            .ok_or_else(|| AppError::unauthorized("Invalid email or password"))?;

        // Check if user is active
        if !user.is_active {
            return Err(AppError::unauthorized("Account is deactivated"));
        }

        // Verify password
        if !self.user_service.verify_password(&user, &req.password).await? {
            return Err(AppError::unauthorized("Invalid email or password"));
        }

        // Update last login
        self.user_service.update_last_login(user.id).await?;

        // Generate JWT token
        let token = self.generate_token(&user)?;
        let expires_at = Utc::now().timestamp() + self.config.jwt_expiration;

        Ok(LoginResponse {
            token,
            user: user.into(),
            expires_at,
        })
    }

    pub async fn register(&self, req: CreateUserRequest) -> AppResult<LoginResponse> {
        // Create user
        let user = self.user_service.create_user(req).await?;

        // Generate JWT token
        let token = self.generate_token(&user)?;
        let expires_at = Utc::now().timestamp() + self.config.jwt_expiration;

        Ok(LoginResponse {
            token,
            user: user.into(),
            expires_at,
        })
    }

    pub async fn refresh_token(&self, user_id: Uuid) -> AppResult<LoginResponse> {
        // Find user
        let user = self.user_service
            .find_by_id(user_id)
            .await?
            .ok_or_else(|| AppError::unauthorized("User not found"))?;

        // Check if user is active
        if !user.is_active {
            return Err(AppError::unauthorized("Account is deactivated"));
        }

        // Generate new JWT token
        let token = self.generate_token(&user)?;
        let expires_at = Utc::now().timestamp() + self.config.jwt_expiration;

        Ok(LoginResponse {
            token,
            user: user.into(),
            expires_at,
        })
    }

    pub async fn get_current_user(&self, user_id: Uuid) -> AppResult<UserResponse> {
        let user = self.user_service
            .find_by_id(user_id)
            .await?
            .ok_or_else(|| AppError::unauthorized("User not found"))?;

        Ok(user.into())
    }

    fn generate_token(&self, user: &Model) -> AppResult<String> {
        let now = Utc::now();
        let exp = now + Duration::seconds(self.config.jwt_expiration);

        let claims = Claims {
            sub: user.id,
            email: user.email.clone(),
            role: user.role.to_string(),
            exp: exp.timestamp() as usize,
            iat: now.timestamp() as usize,
        };

        let header = Header::new(Algorithm::HS256);
        let encoding_key = EncodingKey::from_secret(self.config.jwt_secret.as_ref());

        encode(&header, &claims, &encoding_key)
            .map_err(|e| AppError::internal(format!("Token generation failed: {}", e)))
    }

    pub async fn logout(&self, _user_id: Uuid) -> AppResult<()> {
        // In a more sophisticated implementation, you might want to:
        // 1. Add the token to a blacklist
        // 2. Store active sessions in a database
        // 3. Implement token revocation
        
        // For now, we'll just return success since JWT tokens are stateless
        // The client should remove the token from storage
        Ok(())
    }

    pub async fn change_password(&self, user_id: Uuid, req: ChangePasswordRequest) -> AppResult<()> {
        self.user_service.change_password(user_id, req).await
    }

    pub async fn verify_email(&self, user_id: Uuid) -> AppResult<UserResponse> {
        let user = self.user_service
            .find_by_id(user_id)
            .await?
            .ok_or_else(|| AppError::not_found("User not found"))?;

        // In a real implementation, you would:
        // 1. Generate a verification token
        // 2. Send an email with the verification link
        // 3. Verify the token when the user clicks the link
        
        // For now, we'll just mark the email as verified
        let mut user: ActiveModel = user.into();
        user.email_verified = Set(true);
        user.updated_at = Set(chrono::Utc::now().into());

        let user = user.update(self.user_service.get_connection()).await?;
        Ok(user.into())
    }

    pub async fn request_password_reset(&self, email: &str) -> AppResult<()> {
        let _user = self.user_service
            .find_by_email(email)
            .await?
            .ok_or_else(|| AppError::not_found("User not found"))?;

        // In a real implementation, you would:
        // 1. Generate a password reset token
        // 2. Store it in the database with an expiration time
        // 3. Send an email with the reset link
        
        // For now, we'll just return success
        tracing::info!("Password reset requested for email: {}", email);
        Ok(())
    }

    pub async fn reset_password(&self, token: &str, new_password: &str) -> AppResult<()> {
        // In a real implementation, you would:
        // 1. Validate the reset token
        // 2. Check if it's not expired
        // 3. Find the user associated with the token
        // 4. Update the password
        // 5. Invalidate the token
        
        // For now, we'll just return an error since we haven't implemented token storage
        Err(AppError::bad_request("Password reset not implemented"))
    }
}

impl ToString for UserRole {
    fn to_string(&self) -> String {
        match self {
            UserRole::Admin => "admin".to_string(),
            UserRole::User => "user".to_string(),
            UserRole::Moderator => "moderator".to_string(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::database::Database;

    async fn setup_test_services() -> (AuthService, UserService) {
        // This would require a test database setup
        todo!("Setup test services")
    }

    #[tokio::test]
    #[ignore] // Ignore until test database is set up
    async fn test_login_success() {
        let (auth_service, _user_service) = setup_test_services().await;

        let req = LoginRequest {
            email: "test@example.com".to_string(),
            password: "password123".to_string(),
        };

        // This test would require setting up a test user first
        let result = auth_service.login(req).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    #[ignore]
    async fn test_login_invalid_password() {
        let (auth_service, _user_service) = setup_test_services().await;

        let req = LoginRequest {
            email: "test@example.com".to_string(),
            password: "wrongpassword".to_string(),
        };

        let result = auth_service.login(req).await;
        assert!(result.is_err());
    }
}